#!/bin/sh
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

set -e -u

STATE_DIR=/var/run/cros-machine-id-regen
DEFAULT_MACHINE_ID_FILE=/var/lib/dbus/machine-id

bail_if_not_root() {
  if [ "$(id -u)" != 0 ]; then
    echo "$0: this script must be run as root." >&2
    exit 1
  fi
}

ensure_state_dir() {
  mkdir -p "${STATE_DIR}"
  chmod 700 "${STATE_DIR}"
}

is_avahi_running() {
  initctl status avahi | grep -q "^avahi start"
  return $?
}

log() {
  logger -t cros-machine-id-regen "$@"
}

emit_metrics() {
  local reason="$1"
  local seconds_since_last_update="$2"

  local reason_enum_value=0
  local reason_pretty="Unknown"
  case ${reason} in
  network)
    reason_enum_value=1
    reason_pretty="Network"
    ;;
  periodic)
    reason_enum_value=2
    reason_pretty="Periodic"
    ;;
  esac
  metrics_client -s ChromeOS.MachineIdRegen.Reason "${reason_enum_value}"
  if [ "${seconds_since_last_update}" != 0 ]; then
    metrics_client ChromeOS.MachineIdRegen.AgeSeconds \
      "${seconds_since_last_update}" 0 86400 50
    metrics_client ChromeOS.MachineIdRegen.AgeSeconds_${reason_pretty} \
      "${seconds_since_last_update}" 0 86400 50
  fi
}

regen() {
  local machine_id_file="$1"
  local reason="$2"
  local minimum_age_seconds="$3"

  # Read machine uptime and round to nearest integer.
  local uptime_seconds
  uptime_seconds=$(printf %.0f $(cut -d' ' -f1 /proc/uptime))

  # Figure out path to timestamp file.
  timestamp_path=${STATE_DIR}/timestamp$(echo $machine_id_file | tr / _)

  # Read last update and calculate how long since last time.
  local last_update=0
  if [ -e "${timestamp_path}" ]; then
    last_update=$(cat "${timestamp_path}")
  fi
  local seconds_since_last_update=""
  if [ "${last_update}" != 0 ]; then
    seconds_since_last_update=$((uptime_seconds - last_update))
  fi

  # Skip updating if we did so recently.
  if [ -n "${seconds_since_last_update}" ] && \
     [ "${minimum_age_seconds}" != 0 ] && \
     [ "${seconds_since_last_update}" -lt "${minimum_age_seconds}" ]; then
    log "Not regenerating since we did so ${seconds_since_last_update} seconds ago."
    return 0
  fi

  # Regenerate the machine identifier.
  rm -f "${machine_id_file}".new
  dbus-uuidgen --ensure="${machine_id_file}".new
  mv "${machine_id_file}".new "${machine_id_file}"

  # Update state.
  echo "${uptime_seconds}" > "${timestamp_path}"

  # If running, tell avahi to use the new machine-id.
  if is_avahi_running; then
    dbus-send --system --dest=org.freedesktop.Avahi --print-reply \
      --reply-timeout=10000 \
      / org.freedesktop.Avahi.Server.SetHostName \
      string:"$(cat "${machine_id_file}")" > /dev/null
  fi

  # Synchronously run Upstart jobs wanting to run when rotating the id.
  initctl emit cros-machine-id-regenerated

  # Log and emit metrics.
  log "Regenerated ${machine_id_file} (reason: ${reason})."
  emit_metrics "${reason}" "${seconds_since_last_update}"
}

show_help() {
  cat<<EOF
usage: $0 -r REASON [-t MINIMUM_AGE_SECONDS] [-p PATH_TO_FILE]

Regenerates the ${DEFAULT_MACHINE_ID_FILE} file.

Arguments:
  -r   The reason for regenerating, e.g. 'network' or 'periodic'.
  -t   Don't regenerate if last regenerated this many seconds ago.
  -p   Path to machine-id file to use instead of ${DEFAULT_MACHINE_ID_FILE}.
EOF
}


main() {
  # Parse command-line options.
  local reason=""
  local minimum_age_seconds=0
  local machine_id_file="${DEFAULT_MACHINE_ID_FILE}"
  while getopts "ht:r:p:" opt; do
    case "${opt}" in
    h)
      show_help
      exit 0
      ;;
    t)
      minimum_age_seconds=${OPTARG}
      ;;
    r)
      reason=${OPTARG}
      ;;
    p)
      machine_id_file=${OPTARG}
      ;;
    esac
  done

  # Bail if no reason is given.
  if [ -z "${reason}" ]; then
    show_help
    exit 1
  fi

  # Regenerate the machine idenfitier and ensure multiple invocations
  # are serialized.
  bail_if_not_root
  ensure_state_dir
  (
    flock --timeout 15 -n 9
    regen "${machine_id_file}" "${reason}" "${minimum_age_seconds}"
  ) 9>"${STATE_DIR}"/lock
}

main "$@"
